﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#if WINDOWS
using System.Threading.Tasks;
#endif

namespace DARE
{
   
    public abstract class AEmitter: ARenderable
    {
        #region fields
        
        private List<IParticle> m_particlesList = new List<IParticle>();
        private List<IParticle> m_deadParticles = new List<IParticle>();
        private int m_lastEmission = 0;
        private BlendState m_bs = new BlendState();
        private DepthStencilState m_dss = new DepthStencilState();
        protected static DARE.CNode m_transformNode = new CNode(EType.DYNAMIC, false);

        #region fields for instancing

        List<Matrix> instances = new List<Matrix>();
        List<Matrix> particles = new List<Matrix>();
        IParticle currentParticle;
        // To store instance transform matrices in a vertex buffer, we use this custom
        // vertex type which encodes 4x4 matrices as a set of four Vector4 values.
        static VertexDeclaration instanceVertexDeclaration = new VertexDeclaration
        (
            new VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0),
            new VertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 1),
            new VertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 2),
            new VertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 3)
        );
        static VertexDeclaration particleVertexDeclaration = new VertexDeclaration
                (
                    new VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.BlendIndices, 0),
                    new VertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.BlendIndices, 1),
                    new VertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.BlendIndices, 2),
                    new VertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.BlendIndices, 3)
                );
        DynamicVertexBuffer instancedVertexBuffer;
        DynamicVertexBuffer particleVertexBuffer;

        #endregion


        #endregion

        #region properties

        public int Count { get { return m_particlesList.Count; } }

        public int EmitPerEmission { get; set; }

        public int EmissionInterval { get; set; }

        public int MaxParticlesSimultaneous { get; set; }

        public IParticle ReferenceParticle { get; set; }

        public bool IsStarted { get; set; }

        public string Name { get; set; }

        public Vector3 Velocities { get; set; }

        #endregion

        #region ctor

        public  AEmitter(string name)
        {
            IsStarted = false;
            Name = name;
            /*m_bs.AlphaSourceBlend = Blend.One;
            m_bs.AlphaDestinationBlend = Blend.One;
            m_bs.ColorSourceBlend = Blend.One;
            m_bs.ColorDestinationBlend = Blend.One;

            m_bs.AlphaBlendFunction = BlendFunction.Add;
            m_bs.ColorBlendFunction = BlendFunction.Add;*/

            m_dss.DepthBufferWriteEnable = false;
        }

        #endregion

        #region methods

        public void Restart()
        {
            m_lastEmission = EmissionInterval;
            m_particlesList.Clear();
        }

        public void CreatePool()
        {
            for (int i = m_deadParticles.Count + m_particlesList.Count; i < MaxParticlesSimultaneous; ++i)
                m_deadParticles.Add(ReferenceParticle.Clone());
        }

        public abstract AEmitter Clone();


        /// <summary>
        /// This is the method that sets the emitter parameters for the particles (initial position and initial velocity).
        /// </summary>
        /// <param name="particle"></param>
        public abstract void GetParametersFromEmitter(IParticle particle);

        public void Emit()
        {
            CreatePool();
            int i = 0;

            float nbEmissionsLate = 1;// (m_lastEmission / (EmissionInterval < 1 ? 1 : EmissionInterval));

            for (; i < m_deadParticles.Count && i < EmitPerEmission * nbEmissionsLate && m_particlesList.Count < MaxParticlesSimultaneous; ++i)
            {
                m_particlesList.Add(m_deadParticles[i]);
                m_deadParticles[i].CloneParameters(ReferenceParticle);
                GetParametersFromEmitter(m_deadParticles[i]);
                m_deadParticles[i].Start();
                m_deadParticles.Remove(m_deadParticles[i]);
            }

        }

        public override void Update(GameTime gameTime)
        {
            float elapsedTime = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
            if (IsStarted)
            {
                m_lastEmission += (int)elapsedTime;
                if (m_lastEmission >= EmissionInterval)
                {
                    Emit();
                    m_lastEmission = 0;
                }
            }
            int deadCount = m_deadParticles.Count;
#if WINDOWS
            Parallel.For<int>(0, m_particlesList.Count, () => 0, (i, loop, j) =>
            {
                IParticle p = m_particlesList[i];
                if (!p.IsAlive)
                {
                    m_deadParticles.Add(p);
                }
                else
                    p.Update(elapsedTime);
                return 0;
            }, (x) => { }
            );
#else
            for (int i = 0; i < m_particlesList.Count; ++i)
            {
                IParticle p = m_particlesList[i];
                if (!p.IsAlive)
                {
                    m_deadParticles.Add(p);
                }
                else
                    p.Update(elapsedTime);
            }
#endif
            for (; deadCount < m_deadParticles.Count; ++deadCount)
                m_particlesList.Remove(m_deadParticles[deadCount]);

            if (m_particlesList.Count > 0)
            {
                currentParticle = m_particlesList.First();
                instances.Clear();
                particles.Clear();
                foreach (IParticle particle in m_particlesList)
                {
                    instances.Add(Matrix.Transpose(particle.WorldMatrix));
                    particles.Add(new Matrix(particle.Age, 0, 0, 0,
                                            0, 0, 0, 0,
                                            0, 0, 0, 0,
                                            0, 0, 0, 0));
                }


                if (instancedVertexBuffer != null)
                    instancedVertexBuffer.Dispose();
                if (particleVertexBuffer != null)
                    particleVertexBuffer.Dispose();
                instancedVertexBuffer = new DynamicVertexBuffer(CDare.Instance.GraphicsDevice, instanceVertexDeclaration, instances.Count, BufferUsage.WriteOnly);
                instancedVertexBuffer.SetData(instances.ToArray(), 0, instances.Count, SetDataOptions.Discard);
                particleVertexBuffer = new DynamicVertexBuffer(CDare.Instance.GraphicsDevice, particleVertexDeclaration, particles.Count, BufferUsage.WriteOnly);
                particleVertexBuffer.SetData(particles.ToArray(), 0, instances.Count, SetDataOptions.Discard);
            }
            m_transformNode.Update(gameTime);
        }

        

        public override void Draw(GameTime gameTime)
        {
            if (m_particlesList.Count > 0)
            {
                //BlendState bs = CDare.Instance.GraphicsDevice.BlendState;
                DepthStencilState dss = CDare.Instance.GraphicsDevice.DepthStencilState;


               // CDare.Instance.GraphicsDevice.BlendState = m_bs;
                CDare.Instance.GraphicsDevice.DepthStencilState = m_dss;
                CDare.Instance.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
                
#if WINDOWS_PHONE
                    foreach (IParticle part in m_particlesList)
                        part.Draw();
#else
                CDare.Instance.GraphicsDevice.SetVertexBuffers(
                        new VertexBufferBinding(currentParticle.VertexBuffer, 0, 0),
                        new VertexBufferBinding(particleVertexBuffer, 0, 1),
                        new VertexBufferBinding(instancedVertexBuffer, 0, 1));

                CDare.Instance.GraphicsDevice.Indices = currentParticle.IndexBuffer;
                currentParticle.Draw();

                foreach (EffectPass pass in currentParticle.ParticleEffect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    CDare.Instance.GraphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 4, 0, 2, instances.Count);

                }
                //CDare.Instance.GraphicsDevice.BlendState = bs;
                CDare.Instance.GraphicsDevice.DepthStencilState = dss;
#endif
            }
            m_transformNode.Draw(gameTime);
        }

        #endregion
    }
}
